package com.ideaaedi.zoo.example.springboot.seata_dynamicds.alpha.service;

import com.ideaaedi.commonspring.transaction.DefaultManualTransactionSupport;
import com.ideaaedi.zoo.diy.artifact.dynamic.datasource.datasource.DatasourceSwitcher;
import com.ideaaedi.zoo.diy.artifact.seata.transaction.SeataManualTransactionSupport;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds.alpha.feign.BetaFeignClient1;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds.alpha.feign.BetaFeignClient2;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds.alpha.feign.BetaFeignClient3;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds.alpha.feign.RawHttpRequest4;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds.alpha.po.StudentPO;
import com.ideaaedi.zoo.example.springboot.seata_dynamicds.alpha.po.TeacherPO;
import io.seata.spring.annotation.GlobalTransactional;
import jakarta.annotation.Resource;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.stereotype.Service;
import org.springframework.transaction.TransactionDefinition;
import org.springframework.transaction.annotation.Transactional;

@Service
public class BizService {
    
    @Autowired
    BizService4Basic bizService4Basic;
    
    @Resource
    private BizService bizService;
    
    @Resource
    private BetaFeignClient1 betaFeignClient1;
    
    @Resource
    private BetaFeignClient2 betaFeignClient2;
    
    @Resource
    private BetaFeignClient3 betaFeignClient3;
    
    @Resource
    private RawHttpRequest4 betaFeignClient4;
    
    /**
     * <pre>
     * seata + dynamic-datasource 使用步骤：
     *
     *  本方法测试的流程为： test（默认数据源A） -- app内调用 --> student1（默认数据源A）
     *                                       -- app内调用 --> teacher1（数据源B）
     *                                       -- rpc调用其它app -- app内调用 --> student2（数据源C）
     *                                                        -- app内调用 --> student2（数据源D）
     *
     *  第一步：在test方法上使用 @Transactional + @GlobalTransactional
     *
     *  第二步：1.在student1方法上加上@Transactional
     *            说明：因为和test方法是同一个数据源，所以对于本次调用来说，student1上
     *                 加不加@Transactional都可以，加了事务也会默认合并进当前test的事务里；
     *                 这里加上，是因为在实际应用中，student1方法可能还会被其它入口进行调用
     *
     *         2（推荐）.在teacher1方法上加上@DS("数据源B")  + @Transactional注解
     *                  说明：@DS用于切换数据源、@Transactional是开启本地事务管理，
     *                       因为考虑到，teacher1方法可能还会被其它入口进行调用在test
     *                       方法内，调用teacher1方法时，手动切换至目标数据源，然后开
     *                       启一个新的本地事务，如本文中{@link #addSomeAlphaData()}
     *                       的示例一般
     *                  这里做的好处是：teacher1方法的事务不用无脑的新开，其它地方调
     *                                用teacher1方法时，业务上不需要再考虑新开事务的影响
     *                  这里做的坏处是：这里调用起来会多两行代码
     *
     *         2（不推荐）.在teacher1方法上加上@DS("数据源B")  + @Transactional注解，并指
     *                    定@Transactional的propagation为Propagation.REQUIRES_NEW
     *                    说明：@DS用于切换数据源、@Transactional(propagation = Propagation.REQUIRES_NEW)
     *                         用于开启一个新的事务；因为是不同的数据库，所以需要开启新的事
     *                         务，不能直接用test上的事务，否者会因为不同数据源的语法不兼
     *                         容、库表不存在等问题导致报错
     *                    这里做的好处是：这里调用起来简单
     *                    这里做的坏处是：如果其它入口需要调用teacher1方法，那么需要考虑teacher1方法新开事务导致的影响
     * </pre>
     */
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void test() {
        // 1. 本地添加Alpha数据成功
        addSomeAlphaData();
        
        // 2. feign调用beta微服务添加beta数据
        // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
        //betaFeignClient1.addSomeBetaData();
        //betaFeignClient2.addSomeBetaData();
        //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
        betaFeignClient4.addSomeBetaData();
        
        
        // 3. 本地添加alpha数据后，抛出异常 1 / 0
        addSomeAlphaData();
        System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
    }
    
    
    /**
     * 测试GlobalTransactional注解嵌套
     * <p>
     *     结论：可嵌套使用
     * </p>
     */
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void testNested() {
        addSomeAlphaData();
        
        // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
        //betaFeignClient1.addSomeBetaData();
        //betaFeignClient2.addSomeBetaData();
        //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
        betaFeignClient4.addSomeBetaData();
        
        /*
         * Transactional可以嵌套使用，那么GlobalTransactional是否可以签到使用呢？
         * 这里进行测试
         */
        bizService.addAlphaAndBetaData();
    
        addSomeAlphaData();
        System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
    }
    
    /**
     * 手动事务测试。效果等价于 {@link #test()}
     */
    public void testManual() {
        SeataManualTransactionSupport.create().voidExec(() -> {
            addSomeAlphaData();
            
            // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
            //betaFeignClient1.addSomeBetaData();
            //betaFeignClient2.addSomeBetaData();
            //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
            betaFeignClient4.addSomeBetaData();
    
            addSomeAlphaData();
            System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
        });
    }
    
    
    /**
     * 手动事务测试。效果等价于 {@link #testNested()}
     */
    public void testNestedManual() {
        SeataManualTransactionSupport.create().voidExec(() -> {
            addSomeAlphaData();
            
            // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
            //betaFeignClient1.addSomeBetaData();
            //betaFeignClient2.addSomeBetaData();
            //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
            betaFeignClient4.addSomeBetaData();
    
            addSomeAlphaDataManual();
    
            addSomeAlphaData();
            System.err.println(1 / 0);// todo 在这里打断点，进入断点后观察两张表的数据；然后放行断点跑完请求，再观察两张表的数据； 对比可知：分布式事务生效了
        });
    }
    
    @Transactional(rollbackFor = Exception.class)
    @GlobalTransactional(rollbackFor = Exception.class)
    public void addAlphaAndBetaData() {
        addSomeAlphaData();
        
        // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
        //betaFeignClient1.addSomeBetaData();
        //betaFeignClient2.addSomeBetaData();
        //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
        betaFeignClient4.addSomeBetaData();
    }
    
    /**
     * 手动事务测试。效果等价于 {@link #addAlphaAndBetaData()}
     */
    public void addSomeAlphaDataManual() {
        SeataManualTransactionSupport.create().voidExec(() -> {
            addSomeAlphaData();
            
            // 示例几种常用的 restful调用远程服务的方式, 这几种方式seata都是支持的； 当然seata还支持更多的restful调用远程服务的方式，详见seata官网
            //betaFeignClient1.addSomeBetaData();
            //betaFeignClient2.addSomeBetaData();
            //OnceFeignProvider.voidExec(() -> betaFeignClient3.addSomeBetaData(), "http://127.0.0.1:8081");
            betaFeignClient4.addSomeBetaData();
            
        });
    }
    
    /**
     * 写入一些数据
     */
    public void addSomeAlphaData() {
        int id = (int)System.currentTimeMillis() % 1000000;
        // 插入数据
        bizService4Basic.insertStudent(StudentPO.builder().id(id).name("Alpha 夏洛").build()); // 数据库 mysql  db_one
    
        // 切换到指定的数据源下，执行逻辑
        DatasourceSwitcher.switchTo("oracleAlpha").voidExec(() -> {
            
                    // 因为是不同的数据源，所以需要新开一个spring事务
                    DefaultManualTransactionSupport.create(TransactionDefinition.PROPAGATION_REQUIRES_NEW).voidExec(
                            () -> bizService4Basic.insertTeacher(TeacherPO.builder().id(id).name("Alpha 蒋老师").build()) // 数据库 oracle  ROOT.teacher
                    );
                });
    }
}
